#ifndef _CTREEWIDGET_H_
#define _CTREEWIDGET_H_

#include <buola/widgets/cwidget.h>

#include <buola/gui/cicon.h>
#include <buola/image/cimage.h>

namespace buola { namespace gui {

///\addtogroup widgetclass
///@{

class CTreeWidget : public CWidget
{
public:
    template<typename tType>
    class SColumnData;

    class SNode;
    
    struct SField
    {
        int mIndex;
        CPropertyField *mField;

        double mX;
        double mW;
        double mMinW;
    };

    CTreeWidget()
    {
        mTWNextIndex=0x1000000;
        mTWSelected=0;
        mTWSelectedNode=nullptr;

        mColumnData.mCount=0;
    }

    virtual ~CTreeWidget()
    {
        RemoveAllNodes();
    }

    void TWCopyTree(CTreeWidget*);
protected:
    int GetFieldNum(int);

public:
    int GetFieldCount()                     {   return mTWF.size(); }
    int GetNodeCount()                      {   return mTWN.size(); }
    const std::vector<SField*> &GetFields() {   return mTWF;            }
    const std::vector<SNode*> &GetNodes()   {   return mTWN;            }

//Managing fields
    int AddField(CPropertyField*,int=-1);
    bool RemoveField(int);
    void RemoveAllFields();

//Managing nodes
    SNode *NewNode(SNode*,const std::wstring&,PIcon=nullptr,int=-1);
    SNode *AddNode(SNode*);
    SNode *AddNode(SNode*,const std::wstring&,PIcon=nullptr,int=-1);

    void RemoveNode(SNode*);
    bool RemoveNodeByID(int);
    void RemoveAllNodes();

//Finding and retrieving nodes
    int GetSel();
    std::wstring GetSelText();
    SNode *GetSelNode();

    void SetNodeText(int,const std::wstring&);
    std::wstring GetNodeText(int);

    SNode *GetNode(int);
    SNode *FindNode(const std::wstring&);

    SField *GetField(int);

//Node selection
    virtual void SelectNode(SNode*);
    virtual void SelectNode(int);
    virtual void ToggleNode(SNode*);
    virtual void ToggleNode(int);

    virtual void SelectNodes(const std::vector<SNode*> &pNodes);

public:
    virtual void SelectAll();
    virtual void UnselectAll();

private:
    virtual void SelectNodesUnder(const std::vector<SNode*> &pNodes,SNode *pUnder);
    virtual void UnselectAllUnder(SNode *pUnder);
    virtual void SelectAllUnder(SNode *pUnder);

    std::size_t AcquireColumnDataSlot(void*);
    void ReleaseColumnDataSlot(std::size_t pSlot);
    
protected:
//Events
    virtual void OnNodeSelected(SNode*)     {}
    virtual void OnNodeExpanded(SNode*) {}
    virtual void OnNodeExpandedFirstTime(SNode*)    {}
    virtual void OnTreeChanged() {}
    virtual void OnNodeChanged(SNode*)  {}
    virtual void OnNodeRemoved(SNode*)  {}
    virtual void OnNodeAdded(SNode*)   {}
    virtual void OnFieldsChanged()          {}

protected:
    std::vector<SField*> mTWF;
    std::vector<SNode*> mTWN;

    int mTWNextIndex;
    int mTWSelected;

    SNode *mTWSelectedNode;

    struct
    {
        std::vector<void*> mSlots;
        int mCount;
    } mColumnData;

    friend class SNode;

    template<typename tType>
    friend class SColumnData;
};

class CTreeWidget::SNode
{
public:
    SNode(CTreeWidget*,SNode*);
    ~SNode();

private:
    //Managing children
    void AddChild(SNode*);
    void RemoveChild(SNode*);

public:
    //Finding nodes
    SNode *GetNext();
    SNode *GetNextVisible();
    SNode *GetPrevious();
    SNode *GetPreviousVisible();

    SNode *FindNode(int);
    SNode *FindNode(const std::wstring&);

    //Expanding nodes
    bool CanBeExpanded()    {   return (mChildren.size()||mExpandable); }
    void ExpandTo();
    void Expand();
    void Collapse();

    //Managing fields and values
    void AddField(CPropertyField*);

    void SetColumnData(std::size_t pSlot,void *pData);
    void *GetColumnData(std::size_t pSlot);
    
    //Setting values
public:
    UAny &Value(int);
    std::wstring ValueAsText(int);

    void SetText(const std::wstring &pText);

    //Sorting
    static int SortAscending(const void*,const void*);
    static int SortDescending(const void*,const void*);

    //Tree position
    SNode *mParent;
    std::vector<SNode*> mChildren;
    SNode *mPrevious;
    SNode *mNext;   //At same level
    int mDepth;
    
    //Reference
    CTreeWidget *mTree;
    int mIndex;
    
    //Data
    std::wstring mText;
    PIcon mIcon;
    
    std::vector<std::unique_ptr<UAny>> mValues;
    std::vector<void*> mColumnData;
    
    //Geometry
    SNode *mNextVisible;
    void *mRendererData;
    
    //Properties
    bool mExpandable;           //if it can be expanded even with no children
    bool mSelected;
    bool mExpanded;
    bool mOnceExpanded;
    
    friend class CTreeWidget;
};

template<typename tType>
class CTreeWidget::SColumnData
{
public:
    SColumnData()
        :   mWidget(nullptr)
        ,   mSlot(-1)
    {}
    
    SColumnData(CTreeWidget *pWidget,const tType &pDefault=tType())
        :   mWidget(pWidget)
        ,   mSlot(pWidget->AcquireColumnDataSlot(this))
        ,   mDefaultValue(pDefault)
    {}

    ~SColumnData()
    {
        if(mWidget)
            mWidget->ReleaseColumnDataSlot(mSlot);
    }

    void Init(CTreeWidget *pWidget,const tType &pDefault=tType())
    {
        if(mWidget)
            mWidget->ReleaseColumnDataSlot(mSlot);

        mWidget=pWidget;
        mSlot=pWidget->AcquireColumnDataSlot(this);
        mDefaultValue=pDefault;
    }

    tType &operator[](SNode *pNode)
    {
        tType *lData=(tType*)pNode->GetColumnData(mSlot);

        if(!lData)
        {
            lData=new tType(mDefaultValue);
            pNode->SetColumnData(mSlot,lData);
        }

        return *lData;
    }

private:
    CTreeWidget *mWidget;
    std::size_t mSlot;
    tType mDefaultValue;
};

///@}

/*namespace gui*/ } /*namespace buola*/ }

#endif
